Skip to content

feat(bmi270): Implement FOC and CRT calibration features for bmi270#595

Merged
finger563 merged 5 commits intoesp-cpp:mainfrom
Concode0:feature/bmi270-foc-crt
Feb 14, 2026
Merged

feat(bmi270): Implement FOC and CRT calibration features for bmi270#595
finger563 merged 5 commits intoesp-cpp:mainfrom
Concode0:feature/bmi270-foc-crt

Conversation

@Concode0
Copy link
Contributor

Description

Added Fast Offset Compensation (FOC) and Component Re-Trim (CRT) calibration
features to the BMI270 inertial measurement unit driver. These features provide
hardware-level offset and sensitivity calibration for both accelerometer and
gyroscope sensors.

Motivation and Context

BMI270 sensors experience manufacturing tolerance variations that affect
measurement accuracy. The built-in calibration features compensate for:

  • Accelerometer offset: Systematic bias in acceleration readings
  • Gyroscope offset: Drift and bias in angular rate measurements
  • Gyroscope sensitivity: Gain variations due to component tolerances and temperature effects

But espp/bmi270 doesn't support these feature.

Tested and verified against Bosch Sensortec's official BMI270 Sensor API library
implementation.

How has this been tested?

Using my esp32c3 based imu board, change example codes with calibration example.

Screenshots (if appropriate, e.g. schematic, board, console logs, lab pictures):

[BMI270 Example/I][0.306]: Starting example!
[BMI270 Example/I][0.316]: Found BMI270 at address: 0x68
[Bmi270/I][0.316]: Setting low power mode to disabled
[Bmi270/I][0.326]: Loading BMI270 configuration file (8192 B), this may take some time...
[Bmi270/I][0.546]: Configuration file loaded successfully
[Bmi270/I][0.546]: BMI270 initialized successfully
[BMI270 Example/I][0.546]: Performing Accelerometer FOC...
[Bmi270/I][4.396]: Accel FOC completed. Offsets: X=-10, Y=9, Z=2
[BMI270 Example/I][4.396]: Accelerometer FOC completed successfully
[BMI270 Example/I][4.396]: Performing Gyroscope FOC...
[Bmi270/I][10.806]: Gyro FOC completed. Offsets: X=-2, Y=-1, Z=0
[BMI270 Example/I][10.806]: Gyroscope FOC completed successfully
[BMI270 Example/I][10.806]: Performing CRT...
[Bmi270/I][10.856]: CRT: After setup GYR_CRT_CONF=0x05, bit3(rdy_for_dl)=0
[Bmi270/I][10.876]: CRT: Iter[0] GYR_CRT_CONF=0x05, bit3=0
[Bmi270/I][11.056]: CRT: Iter[9] GYR_CRT_CONF=0x05, bit3=0
[Bmi270/I][11.256]: CRT: Iter[19] GYR_CRT_CONF=0x05, bit3=0
[Bmi270/W][14.856]: CRT: Ready for Download bit didn't toggle - may still continue
[Bmi270/I][14.916]: CRT: Completion detected at iteration 0
[Bmi270/I][14.916]: CRT completed successfully
[BMI270 Example/I][14.916]: CRT completed successfully

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation Update
  • Hardware (schematic, board, system design) change
  • Software change

Checklist:

  • My change requires a change to the documentation.
  • I have added / updated the documentation related to this change via either README or WIKI

Software

  • I have added tests to cover my changes.
  • I have updated the .github/workflows/build.yml file to add my new test to the automated cloud build github action.
  • All new and existing tests passed.
  • My code follows the code style of this project.

@github-actions
Copy link

✅Static analysis result - no issues found! ✅

@Concode0 Concode0 force-pushed the feature/bmi270-foc-crt branch from 7ccbcf2 to f4e0119 Compare January 30, 2026 10:31
@Concode0 Concode0 changed the title Implement FOC and CRT calibration features for bmi270 feat(bmi270): Implement FOC and CRT calibration features for bmi270 Jan 30, 2026
@finger563 finger563 added enhancement New feature or request bmi270 labels Feb 7, 2026
// 1. The device must be COMPLETELY STATIONARY on a flat surface.
// 2. Enable these lines only when you need calibration (e.g., once after assembly).
// ---------------------------------------------------------------------------
#if 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please put this behind a KConfig variable for the example that defaults to true? that way the automated builds in CI catch any potential breaking API changes and the example naturally tests the API.

Comment on lines 145 to 162
// print kalman filter outputs
text += fmt::format("{:03.3f},{:03.3f},{:03.3f},", (float)orientation.x, (float)orientation.y,
(float)orientation.z);
text += fmt::format("{:03.3f},{:03.3f},{:03.3f},", (float)gravity_vector.x,
(float)gravity_vector.y, (float)gravity_vector.z);

auto madgwick_orientation = madgwick_filter_fn(dt, accel, gyro);
float roll = madgwick_orientation.roll;
float pitch = madgwick_orientation.pitch;
float yaw = madgwick_orientation.yaw;
float vx = sin(pitch);
float vy = -cos(pitch) * sin(roll);
float vz = -cos(pitch) * cos(roll);

// print madgwick filter outputs
text += fmt::format("{:03.3f},{:03.3f},{:03.3f},", roll, pitch, yaw);
text += fmt::format("{:03.3f},{:03.3f},{:03.3f}", vx, vy, vz);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason you removed these prints / calculations?

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds calibration capabilities to the espp::Bmi270 driver to improve sensor accuracy by leveraging BMI270’s on-chip routines (FOC for accel/gyro offsets and CRT for gyro gain trim), along with example/docs updates to demonstrate usage.

Changes:

  • Added new public driver APIs for accelerometer/gyroscope FOC, CRT, gyro self-test, and offset/gain enable + offset read/write helpers.
  • Extended BMI270 detail types (FOC target struct, CRT/status enums) and added fmt formatting for CRT status.
  • Updated the example and documentation to show optional calibration usage and expand “Advanced Features” docs.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 12 comments.

File Description
components/bmi270/include/bmi270_detail.hpp Adds new calibration-related structs/enums and fmt formatter for CRT trigger status.
components/bmi270/include/bmi270.hpp Implements FOC/CRT/self-test and exposes offset/gain control APIs; adjusts register map for new features.
components/bmi270/example/main/bmi270_example.cpp Adds optional (disabled) calibration demo block; modifies CSV output behavior.
components/bmi270/example/README.md Documents calibration features and adds troubleshooting/advanced-feature guidance.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 532 to 539
// Invert
int8_t off_x = static_cast<int8_t>(-(delta_x / 64));
int8_t off_y = static_cast<int8_t>(-(delta_y / 64));
int8_t off_z = static_cast<int8_t>(-(delta_z / 64));

// Write offset registers
uint8_t data[3] = {static_cast<uint8_t>(off_x), static_cast<uint8_t>(off_y), static_cast<uint8_t>(off_z)};
write_many_to_register(static_cast<uint8_t>(Register::ACC_OFF_COMP_0), data, 3, ec);
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

off_x/off_y/off_z are computed as int32/64 then cast directly to int8_t. For realistic deltas (e.g., ~±16384 at 2g), this can exceed the int8_t range and wrap, producing incorrect offsets. Clamp the computed value to [-128, 127] before casting/writing.

Copilot uses AI. Check for mistakes.
Comment on lines +476 to +485
// Prepare for FOC
if (saved_aps) {
clear_bits_in_register(static_cast<uint8_t>(Register::PWR_CONF), 0x01, ec);
if (ec) return false;
}
if (!saved_acc_en) {
set_bits_in_register(static_cast<uint8_t>(Register::PWR_CTRL), ACC_ENABLE, ec);
if (ec) return false;
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This calibration routine mutates device state (APS bit, ACC enable, ACC_CONF/RANGE) and has multiple early return false paths. On any failure after state changes, the original settings may not be restored, leaving the device in an unexpected mode. Consider using a scope-guard/RAII cleanup so APS, sensor enables, and config are restored on all exit paths (success or error).

Copilot uses AI. Check for mistakes.
Comment on lines 637 to 638
if (!saved_gyr_en) enable_gyroscope(false, ec);
if (saved_aps) set_bits_in_register(static_cast<uint8_t>(Register::PWR_CONF), 0x01, ec);
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In perform_gyro_foc, the restoration calls (enable_gyroscope(false, ec) and re-enabling APS) are not followed by if (ec) return false;. As written, the function can log success and return true even if restoration fails and ec is set.

Suggested change
if (!saved_gyr_en) enable_gyroscope(false, ec);
if (saved_aps) set_bits_in_register(static_cast<uint8_t>(Register::PWR_CONF), 0x01, ec);
if (!saved_gyr_en) {
enable_gyroscope(false, ec);
if (ec) return false;
}
if (saved_aps) {
set_bits_in_register(static_cast<uint8_t>(Register::PWR_CONF), 0x01, ec);
if (ec) return false;
}

Copilot uses AI. Check for mistakes.
uint8_t status = (status_reg & 0x38) >> 3;

if (status != 0x00) {
logger_.error("CRT: Failed");
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When CRT fails, the code logs only "CRT: Failed" without the decoded status bits (already extracted into status). Including the status value (and/or mapping it to GTriggerStatus) would make failures diagnosable.

Suggested change
logger_.error("CRT: Failed");
logger_.error("CRT: Failed, status=0x{:02X}", status);

Copilot uses AI. Check for mistakes.
Comment on lines 177 to 185
// print time and raw IMU data
std::string text = "";
text += fmt::format("{:.3f},", now / 1'000'000.0f);
text +=
fmt::format("{:02.3f},{:02.3f},{:02.3f},", (float)accel.x, (float)accel.y, (float)accel.z);
text += fmt::format("{:03.3f},{:03.3f},{:03.3f},", (float)gyro.x, (float)gyro.y, (float)gyro.z);
text += fmt::format("{:02.1f},", temp);
// print kalman filter outputs
text += fmt::format("{:03.3f},{:03.3f},{:03.3f},", (float)orientation.x, (float)orientation.y,
(float)orientation.z);
text += fmt::format("{:03.3f},{:03.3f},{:03.3f},", (float)gravity_vector.x,
(float)gravity_vector.y, (float)gravity_vector.z);

auto madgwick_orientation = madgwick_filter_fn(dt, accel, gyro);
float roll = madgwick_orientation.roll;
float pitch = madgwick_orientation.pitch;
float yaw = madgwick_orientation.yaw;
float vx = sin(pitch);
float vy = -cos(pitch) * sin(roll);
float vz = -cos(pitch) * cos(roll);

// print madgwick filter outputs
text += fmt::format("{:03.3f},{:03.3f},{:03.3f},", roll, pitch, yaw);
text += fmt::format("{:03.3f},{:03.3f},{:03.3f}", vx, vy, vz);

fmt::print("{}\n", text);

Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CSV data line now only prints time + accel + gyro + temp, but the header printed later still lists Kalman/Madgwick orientation and gravity columns. This makes the output CSV invalid/misleading. Either restore printing of those values in the data rows or update the header (and remove now-unused orientation/gravity locals).

Copilot uses AI. Check for mistakes.
- Reduce the burst write size in the configuration to limit stack usage.
```cpp
espp::Bmi270::Config config;
config.burst_write_size = 128; // Use 128-byte chunks (default is 0/unlimited)
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The note says burst_write_size default is "0/unlimited", but in the driver 0 maps to a single chunk of config_file_size bytes (currently 8192). Consider updating this wording to avoid implying an unbounded write size.

Suggested change
config.burst_write_size = 128; // Use 128-byte chunks (default is 0/unlimited)
config.burst_write_size = 128; // Use 128-byte chunks (default 0 = one chunk of config_file_size bytes, e.g. 8192)

Copilot uses AI. Check for mistakes.

/// Perform Component Re-Trim (CRT)
/// @param ec The error code to set if an error occurs
/// @return True if successful
Copy link
Contributor

@finger563 finger563 Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth adding a note to the API docs that this function may wait / block for over 2.5 seconds - and however long the calibration file takes to write as well


/// Perform gyroscope Fast Offset Compensation (FOC)
/// @param ec The error code to set if an error occurs
/// @return True if successful
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth adding a note to the API docs that this function will take / block for >5.5 seconds

/// Perform accelerometer Fast Offset Compensation (FOC)
/// @param accel_g_value Target values for FOC
/// @param ec The error code to set if an error occurs
/// @return True if successful
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth adding a note to the API docs for this function that it will take / block for >= 2.5 seconds

return status == 0x00;
}

/// Abort CRT or Gyro Self Test
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add API docs

return !ec;
}

/// Perform Gyro Self Test
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add API docs

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And please mention that this function will block for >= 2 seconds

Copy link
Contributor

@finger563 finger563 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! Overall this code looks really good, just have a few suggestions for the example and for API docs

@Concode0
Copy link
Contributor Author

Hi @finger563, sorry for the delay!

I've addressed all your feedback, including RAII (ScopeGuard), input clamping, and documentation updates.

Also, I restored the example code—removing it was a mistake due to a local build error, but it's fixed now. Ready for review again. Thanks!

@Concode0 Concode0 requested a review from finger563 February 11, 2026 04:14
Copy link
Contributor

@finger563 finger563 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing the comments, this looks great! There are a couple documentation updates but once those are done I'll merge this and cut a release!

}

/// Abort CRT or Gyro Self Test
bool abort_crt_gyro_self_test(std::error_code &ec) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add API docs to this function

bool set_gyro_offset(int16_t x, int16_t y, int16_t z, std::error_code &ec) {
std::lock_guard<std::recursive_mutex> lock(base_mutex_);

// Check for valid 10-bit range [-512, 511]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please indicate in the API docs for this function the valid range?

@Concode0 Concode0 requested a review from finger563 February 14, 2026 03:33
@finger563 finger563 merged commit 9a907ac into esp-cpp:main Feb 14, 2026
100 of 101 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bmi270 enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants